/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/*
 * GeographicPopulationComponent.java
 *
 * Created on 8/Dez/2011, 10:51:45
 */

package intopo.evogame.partnerselection.tools.config;

import intopo.evogame.partnerselection.config.TextConfigFilePlainPlayersEditor;
import intopo.evogame.partnerselection.config.TextConfigFilePlainPlayersEditor.NodeParameters;
import intopo.evogame.partnerselection.population.GeographicPopulation.Geometry;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.beans.*;
import java.io.Serializable;
import javax.swing.ButtonGroup;
import javax.swing.JMenuItem;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.SwingConstants;

/**
 * A component that displays information about the parameters of a geographic population.
 *
 * @author <a href="mailto:mariano.pedro@gmail.com">Pedro Mariano</a>
 *
 * @version 1.0 8/Dez/2011
 */
public class GeographicPopulationComponent
	extends javax.swing.JComponent
	implements
		Serializable,
		javax.swing.Scrollable
{
	/**
	 * Parameters of the geographic population that are shown by this component.
	 */
//	final private GeographicParameters parameters;
	private TextConfigFilePlainPlayersEditor configFile;
	/**
	 * Size in pixels of the node.
	 */
	private static final int NODE_SIZE = 32;
	/**
	 * Horizontal coordinate of the selected node.
	 */
	private int selectedNodeX;
	/**
	 * Vertical coordinate of the selected node.
	 */
	private int selectedNodeY;
	/**
	 * Information displayed by this component.
	 */
	private Information information;
	/**
	 * Mode of operation.  When the user clicks the mouse a certain operation is performed.
	 */
	private Operation operation;
	/**
	 * Scale used when drawing node information;
	 */
	private float scale;
	/**
	 * The node that is used by the set node operation.
	 */
	private NodeParameters clipboard;
	/**
	 * Horizontal coordinate of the node that is in the clipboard.
	 */
	private int clipboardX;
	/**
	 * Vertical coordinate of the node that is in the clipboard.
	 */
	private int clipboardY;
	private PropertyChangeSupport propertySupport;
	/**
	 * Constructor used by the GUI builder.  Constructs a geographic parameters that is not referenced by a {@code ConfigParameters} instance.
	 */
	public GeographicPopulationComponent ()
	{
		this.configFile = null;
		propertySupport = new PropertyChangeSupport (this);
		System.out.println ("GeographicPopulationComponent GUI builder constructor");
	}
	/**
	 * Cosntructs a new component that displays information from the given geographic population.
	 * @param parameters
	 */
	GeographicPopulationComponent (TextConfigFilePlainPlayersEditor configFile)
	{
		this.configFile = configFile;
		this.selectedNodeX = 0;
		this.selectedNodeY = 0;
		this.scale = 1f;
		this.information = Information.CARRYING_CAPACITY;
		this.operation = Operation.SELECT_NODE;
		this.clipboard = getSelectedNode ();
		this.clipboardX = 0;
		this.clipboardY = 0;
		Dimension size = new java.awt.Dimension (
			this.configFile.getPopulation ().getWidth () * GeographicPopulationComponent.NODE_SIZE + 1,
			this.configFile.getPopulation ().getHeight () * GeographicPopulationComponent.NODE_SIZE + 1);
		this.setPreferredSize (size);
		propertySupport = new PropertyChangeSupport (this);
		Listener l = new Listener ();
		this.addMouseListener (l);
		this.addMouseWheelListener (l);
		this.setComponentPopupMenu (new Popmenu ());
	}
	/**
	 * Called when the user changed the parameters of the geographic population.
	 *
	 * <p> Calculates the new preferred size, revalidates the component because of the scrollpane,
	 * and repaints the component.
	 */
	public void parametersChanged ()
	{
		Dimension size = new java.awt.Dimension (
			this.configFile.getPopulation ().getWidth () * GeographicPopulationComponent.NODE_SIZE + 1,
			this.configFile.getPopulation ().getHeight () * GeographicPopulationComponent.NODE_SIZE + 1);
		this.setPreferredSize (size);
		this.revalidate ();
		this.repaint ();
	}
	/**
	 * Update the clipboard because the user selected a new game.  This method must be called after calling method {@code GeographicParameters.descriptionChanged()}.
	 *
	 * @see GeographicParameters#descriptionChanged()
	 */
	void descriptionChanged ()
	{
		this.clipboard = this.getSelectedNode ();
	}
	@Override
	public void paint (Graphics g)
	{
		if (this.configFile == null) {
			return ;
		}
		int x, y;
		float ax =(this.configFile.getPopulation ().getGeometry () == Geometry.HEXAGONAL ? 0.5f : 0);
		final int w = this.configFile.getPopulation ().getWidth ();
		final int h = this.configFile.getPopulation ().getHeight ();
		final Graphics2D g2d = (Graphics2D) g;
		// draw information
		for (y = 0; y < h; y++) {
			for (x = 0; x < w; x++) {
				NodeParameters node = this.configFile.getPopulation ().getNode (x, y);
				g.setClip (0, 0, GeographicPopulationComponent.NODE_SIZE,
					GeographicPopulationComponent.NODE_SIZE);
				this.information.paint (this, g, node, x, y);
				g.translate (GeographicPopulationComponent.NODE_SIZE, 0);
			}
			g.translate (- w * GeographicPopulationComponent.NODE_SIZE, GeographicPopulationComponent.NODE_SIZE);
		}
		g.translate (0, - h * GeographicPopulationComponent.NODE_SIZE);
		g.setClip (null);
		// draw grid
		g.setColor (Color.BLUE);
		for (y = h; y >= 0; y--) {
			g.drawLine (
				Math.round (y * ax * GeographicPopulationComponent.NODE_SIZE),
				y * GeographicPopulationComponent.NODE_SIZE,
				Math.round (y * ax * GeographicPopulationComponent.NODE_SIZE)
				+ w * GeographicPopulationComponent.NODE_SIZE,
				y * GeographicPopulationComponent.NODE_SIZE);
		}
		if (this.configFile.getPopulation ().getGeometry () == Geometry.HEXAGONAL) {

		}
		else {
			for (x = w; x >= 0; x--) {
				g.drawLine (
					x * GeographicPopulationComponent.NODE_SIZE,
					0,
					x * GeographicPopulationComponent.NODE_SIZE,
					h * GeographicPopulationComponent.NODE_SIZE);
			}
		}
		// draw selected node
		g.setColor (Color.RED);
		g.drawRect (
			this.selectedNodeX * GeographicPopulationComponent.NODE_SIZE
				+ Math.round (this.selectedNodeY * ax * GeographicPopulationComponent.NODE_SIZE),
			this.selectedNodeY * GeographicPopulationComponent.NODE_SIZE,
			GeographicPopulationComponent.NODE_SIZE,
			GeographicPopulationComponent.NODE_SIZE);
		// draw node in the clipboard
		g2d.setColor (Color.YELLOW);
		g2d.setStroke (new BasicStroke (1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND, 1f, new float [] {4, 4}, 0));
		g2d.drawRect (
			this.clipboardX * GeographicPopulationComponent.NODE_SIZE
				+ Math.round (this.clipboardY * ax * GeographicPopulationComponent.NODE_SIZE),
			this.clipboardY * GeographicPopulationComponent.NODE_SIZE,
			GeographicPopulationComponent.NODE_SIZE,
			GeographicPopulationComponent.NODE_SIZE);
	}
	/**
	 * Get the selected node.  This node is shown in the node component.
	 */
	NodeParameters getSelectedNode ()
	{
		return this.configFile.getPopulation ().getNode (this.selectedNodeX, this.selectedNodeY);
	}
	/**
	 * Sets the information displayed by this component and asks this component to redisplay.
	 * 
	 * @param information
	 */
	void setInformation (Information information)
	{
		this.information = information;
		this.repaint ();
	}
	/**
	 * Sets the mode of operation of this component.
	 * @param operation
	 */
	void setOperation (Operation operation)
	{
		this.operation = operation;
	}
	void setScale (float value)
	{
		this.scale = value;
		this.repaint ();
	}
	public void addPropertyChangeListener (PropertyChangeListener listener)
	{
		propertySupport.addPropertyChangeListener (listener);
	}

	public void removePropertyChangeListener (PropertyChangeListener listener)
	{
		propertySupport.removePropertyChangeListener (listener);
	}

	@Override
	public Dimension getPreferredScrollableViewportSize ()
	{
		if (this.configFile == null) {
			return new java.awt.Dimension (1, 1);
		}
		Dimension size = new java.awt.Dimension (
			this.configFile.getPopulation ().getHeight () * GeographicPopulationComponent.NODE_SIZE,
			this.configFile.getPopulation ().getWidth () * GeographicPopulationComponent.NODE_SIZE);
		return size;
	}

	@Override
	public int getScrollableUnitIncrement (Rectangle visibleRect, int orientation, int direction)
	{
		//Get the current position.
		int currentPosition = 0;
		if (orientation == SwingConstants.HORIZONTAL) {
			currentPosition = visibleRect.x;
		}
		else {
			currentPosition = visibleRect.y;
		}

		//Return the number of pixels between currentPosition
		//and the nearest tick mark in the indicated direction.
		if (direction < 0) {
			int newPosition =
				currentPosition
				- (currentPosition / GeographicPopulationComponent.NODE_SIZE)
				* GeographicPopulationComponent.NODE_SIZE;
			System.out.println ("getScrollableUnitIncrement() newPosition: " + newPosition);
			return (newPosition == 0) ? GeographicPopulationComponent.NODE_SIZE : newPosition;
		}
		else {
			return ((currentPosition / GeographicPopulationComponent.NODE_SIZE) + 1)
				* GeographicPopulationComponent.NODE_SIZE
				- currentPosition;
		}
	}

	@Override
	public int getScrollableBlockIncrement (Rectangle visibleRect, int orientation, int direction)
	{
		if (orientation == SwingConstants.HORIZONTAL) {
			return visibleRect.width - GeographicPopulationComponent.NODE_SIZE;
		}
		else {
			return visibleRect.height - GeographicPopulationComponent.NODE_SIZE;
		}
	}

	@Override
	public boolean getScrollableTracksViewportWidth ()
	{
		return false;
	}

	@Override
	public boolean getScrollableTracksViewportHeight ()
	{
		return false;
	}

	/**
	 * Information displayed by a {@code GeographicPopulationComponent}.
	 */
	enum Information
	{
		/**
		 * Display the carrying capacity of each node.
		 */
		CARRYING_CAPACITY ("carrying capacity")
		{
			void paint (GeographicPopulationComponent component, Graphics g, NodeParameters node, int x, int y)
			{
				int color = Math.min (Math.round (node.getCarryingCapacity () * component.scale), 255);
				g.setColor (new Color (color, color, color));
				g.fillRect (
					0, 0,
					GeographicPopulationComponent.NODE_SIZE,
					GeographicPopulationComponent.NODE_SIZE);
			}
		},
		/**
		 * Display the number of players in each node.
		 */
		NUMBER_PLAYERS ("number players") {
			@Override
			void paint (GeographicPopulationComponent component, Graphics g, NodeParameters node, int x, int y)
			{
				int color = Math.min (Math.round (node.getNumberPlayers () * component.scale), 255);
				g.setColor (new Color (color, color, color));
				g.fillRect (
					0, 0,
					GeographicPopulationComponent.NODE_SIZE,
					GeographicPopulationComponent.NODE_SIZE);
			}
		},
		/**
		 * Display the number of individuals in each node
		 */
		NUMBER_INDIVIDUALS ("number individuals")
		{
			@Override
			void paint (GeographicPopulationComponent component, Graphics g, NodeParameters node, int x, int y)
			{
				int color = Math.min (Math.round (node.getNumberIndividuals () * component.scale), 255);
				g.setColor (new Color (color, color, color));
				g.fillRect (
					0, 0,
					GeographicPopulationComponent.NODE_SIZE,
					GeographicPopulationComponent.NODE_SIZE);
			}
		};
		/**
		 * Information description used in Graphical User Interface.
		 */
		String description;
		private Information (String description)
		{
			this.description = description;
		}
		/**
		 * Paint the information of the given node using the graphics object.  Parameters {@code x} and {@code y} are the coordinates of the node in the geographic population.
		 * The drawings in the graphics object are clipped in a square with size {@code GeographicPopulationComponent.NODE_SIZE}.
		 * @param g
		 * @param node
		 * @param x
		 * @param y
		 */
		abstract void paint (GeographicPopulationComponent component, Graphics g, NodeParameters node, int x, int y);
	}
	/**
	 * Operations that can be performed when the user clicks this component.
	 */
	enum Operation
	{
		/**
		 * Select the given node to be edited.
		 */
		SELECT_NODE ("select")
		{
			void perform (GeographicPopulationComponent component, MouseEvent e)
			{
				int x = e.getX ();
				int y = e.getY ();
				if (x % GeographicPopulationComponent.NODE_SIZE == 0
					  || y % GeographicPopulationComponent.NODE_SIZE == 0) {
					return ;
				}
				x /= GeographicPopulationComponent.NODE_SIZE;
				y /= GeographicPopulationComponent.NODE_SIZE;
				if (x != component.selectedNodeX || y != component.selectedNodeY) {
					component.selectedNodeX = x;
					component.selectedNodeY = y;
					component.propertySupport.firePropertyChange ("selectedNode", null, component.getSelectedNode ());
					component.repaint ();
					component.clipboard = component.getSelectedNode ();
					component.clipboardX = x;
					component.clipboardY = y;
				}
			}
		},
		/**
		 * Sets some fields of the nodes.  The values are taken from the clipboard.
		 */
		SET_NODE_FIELD ("paste")
		{
			void perform (GeographicPopulationComponent component, MouseEvent e)
			{
				if (changeSelectedNode (component, e)) {
					NodeParameters np = component.getSelectedNode ();
					np.clear ();
					np.setCarryingCapacity (component.clipboard.getCarryingCapacity ());
					for (int i = component.clipboard.getNumberPlayers () - 1; i >= 0; i--) {
						np.addPlayer (component.clipboard.getPlayer (i));
					}
					component.propertySupport.firePropertyChange ("selectedNode", null, np);
					component.repaint ();
				}
			}
		},
		/**
		 * Clears the node carrying capacity and strategies.  The carrying capacity is set to the lowest value.
		 */
		CLEAR_NODE ("clear")
		{
			void perform (GeographicPopulationComponent component, MouseEvent e)
			{
				int x = e.getX ();
				int y = e.getY ();
				if (x % GeographicPopulationComponent.NODE_SIZE == 0
					  || y % GeographicPopulationComponent.NODE_SIZE == 0) {
					return ;
				}
				component.selectedNodeX = x / GeographicPopulationComponent.NODE_SIZE;
				component.selectedNodeY = y / GeographicPopulationComponent.NODE_SIZE;
				NodeParameters np = component.getSelectedNode ();
				np.clear ();
				component.propertySupport.firePropertyChange ("selectedNode", null, np);
				component.repaint ();
			}
		},
		FILL ("fill")
		{
			@Override
			void perform (GeographicPopulationComponent component, MouseEvent e)
			{
				TextConfigFilePlainPlayersEditor.GeographicPopulationParameters pop = component.configFile.getPopulation ();
				for (int y = 0; y < pop.getHeight (); y++) {
					for (int x = 0; x < pop.getWidth (); x++) {
						NodeParameters np = pop.getNode (x, y);
						if (np == component.clipboard) {
							continue;
						}
						np.clear ();
						np.setCarryingCapacity (component.clipboard.getCarryingCapacity ());
						for (int i = component.clipboard.getNumberPlayers () - 1; i >= 0; i--) {
							np.addPlayer (component.clipboard.getPlayer (i));
						}
					}
				}
			}
		},
		ADD ("add")
		{
			@Override
			void perform (GeographicPopulationComponent component, MouseEvent e)
			{
				if (changeSelectedNode (component, e)) {
					NodeParameters np = component.getSelectedNode ();
					np.setCarryingCapacity (
						np.getCarryingCapacity () + component.clipboard.getCarryingCapacity ());
					for (int i = component.clipboard.getNumberPlayers () - 1; i >= 0; i--) {
						np.addPlayer (component.clipboard.getPlayer (i));
					}
					component.propertySupport.firePropertyChange ("selectedNode", null, np);
					component.repaint ();
				}
			}
		};
		String description;
		Operation (String description)
		{
			this.description = description;
		}
		abstract void perform (GeographicPopulationComponent component, MouseEvent e);
		/**
		 * Whenever the user clicks in the geographic component we update the selected node.  Returns {@code false}
		 * if the user clicked in some are that does not correspond
		 * to a node.
		 * @param component
		 * @param e
		 * @return
		 */
		static private boolean changeSelectedNode (GeographicPopulationComponent component, MouseEvent e)
		{
			int x = e.getX ();
			int y = e.getY ();
			if (x % GeographicPopulationComponent.NODE_SIZE == 0
				  || y % GeographicPopulationComponent.NODE_SIZE == 0) {
				return false;
			}
			component.selectedNodeX = x / GeographicPopulationComponent.NODE_SIZE;
			component.selectedNodeY = y / GeographicPopulationComponent.NODE_SIZE;
			return true;
		}
	}
	/**
	 * The popmenu of a geographic population component.  This class constructs a pop menu with all actions that can be done.
	 */
	private class Popmenu
		extends javax.swing.JPopupMenu
		implements ActionListener
	{
		private JRadioButtonMenuItem[] informationMenuItem;
		private JRadioButtonMenuItem[] operationMenuItem;
		private JMenuItem resetScale;
		private Popmenu ()
		{
			int i;
			boolean first;
			ButtonGroup bg;
			this.informationMenuItem = new JRadioButtonMenuItem [Information.values ().length];
			bg = new ButtonGroup ();
			i = 0;
			first = true;
			for (Information info : Information.values ()) {
				informationMenuItem [i] = new JRadioButtonMenuItem (info.description, first);
				informationMenuItem [i].addActionListener (this);
				bg.add (this.informationMenuItem [i]);
				this.add (this.informationMenuItem [i]);
				first = false;
				i++;
			}
			this.addSeparator ();
			this.operationMenuItem = new JRadioButtonMenuItem [Operation.values ().length];
			bg = new ButtonGroup ();
			i = 0;
			first = true;
			for (Operation op : Operation.values ()) {
				this.operationMenuItem [i] = new JRadioButtonMenuItem (op.description, first);
				this.operationMenuItem [i].addActionListener (this);
				bg.add (this.operationMenuItem [i]);
				this.add (this.operationMenuItem [i]);
				first = false;
				i++;
			}
			this.addSeparator ();
			this.resetScale = new JMenuItem ("Reset scale");
			this.resetScale.addActionListener (this);
			this.add (this.resetScale);
		}
		@Override
		public void actionPerformed (ActionEvent e)
		{
			int i = 0;
			for (i = this.informationMenuItem.length - 1; i >= 0; i--) {
				if (e.getSource () == this.informationMenuItem [i]) {
					GeographicPopulationComponent.this.setInformation (Information.values () [i]);
					return ;
				}
			}
			for (i = this.operationMenuItem.length - 1; i >= 0; i--) {
				if (e.getSource () == this.operationMenuItem [i]) {
					GeographicPopulationComponent.this.setOperation (Operation.values () [i]);
					return ;
				}
			}
			if (this.resetScale == e.getSource ()) {
				GeographicPopulationComponent.this.setScale (1f);
			}
		}
	}
	/**
	 * Listener responsible for processing mouse events in a {@code GeographicPopulationComponent}.
	 * Left clicking triggers the selected operation.  Using the mouse wheel changes the scale.
	 */
	private class Listener
		extends java.awt.event.MouseAdapter
		implements java.awt.event.MouseListener
	{
		@Override
		public void mouseWheelMoved (MouseWheelEvent e)
		{
			int amount = e.getWheelRotation ();
			float scale = GeographicPopulationComponent.this.scale;
			GeographicPopulationComponent.this.setScale ((float) (scale * Math.pow (2, amount)));
		}

		@Override
		public void mouseClicked (MouseEvent e)
		{
			GeographicPopulationComponent.this.operation.perform (GeographicPopulationComponent.this, e);
//			int x = e.getX ();
//			int y = e.getY ();
//			selectedNodeX = x / GeographicPopulationComponent.NODE_SIZE;
//			selectedNodeY = y / GeographicPopulationComponent.NODE_SIZE;
//			propertySupport.firePropertyChange ("selectedNode", null, getSelectedNode ());
//			repaint ();
		}

//		@Override
//		public void mousePressed (MouseEvent e) {
//			throw new UnsupportedOperationException ("Not supported yet.");
//		}
//
//		@Override
//		public void mouseReleased (MouseEvent e) {
//			throw new UnsupportedOperationException ("Not supported yet.");
//		}
//
//		@Override
//		public void mouseEntered (MouseEvent e) {
//			throw new UnsupportedOperationException ("Not supported yet.");
//		}
//
//		@Override
//		public void mouseExited (MouseEvent e) {
//			throw new UnsupportedOperationException ("Not supported yet.");
//		}

	}
}

// Local Variables:
// mode: java
// mode: flyspell-prog
// ispell-local-dictionary: "british"
// End:
